package model

import (
	"mail-server/app/config"
	"mail-server/app/utils"
	"net"
	"regexp"
	"strings"
	"time"
)

//接收从客户端传来的邮件
type Mail struct {
	domain  string
	from    string
	to      []string
	subject string
	data    string
	content string
	size    int
	date    string
}

//用于在数据库中保存邮件
type UserMails struct {
	UserMail     string `gorm:"Column:user_email;Type:varchar(30);NOT NULL"`
	EmailFrom    string `gorm:"Column:email_from;Type:varchar(30);NOT NULL"`
	EmailTo      string `gorm:"Column:email_to;Type:varchar(30);NOT NULL"`
	EmailSubject string `gorm:"Column:email_subject;Type:varchar(255);NOT NULL"`
	EmailContent string `gorm:"Column:email_content;Type:text;NOT NULL"`
	EmailSize    int    `gorm:"Column:email_size;Type:bigint(20);DEFAULT NULL"`
	EmailRead    string `gorm:"Column:email_read;Type:char(1);NOT NULL"`
	EmailFromDel string `gorm:"Column:email_from_del;Type:char(1);NOT NULL"`
	EmailToDel   string `gorm:"Column:email_to_del;Type:char(1);NOT NULL"`
	EmailTime    string `gorm:"Column:email_time;Type:datetime;NOT NULL"`
}

func (mail *Mail) Init() {
	mail.domain = ""
	mail.from = ""
	mail.to = []string{}
	mail.subject = ""
	mail.data = ""
	mail.content = ""
	mail.size = 0
	mail.date = ""
}

var SP = " "
var CR = "\r"
var LF = "\n"
var CRLF = CR + LF

//运行
func Run() {
	for true {
		stat, _ := GetServerStatus()
		if stat == "T" {
			SMTPListen()
		} else {
			continue
		}
	}

}

//获取服务开启状态
func GetServerStatus() (status string, err error) {
	row := Db.
		Table("server").
		Select("smtp_on").
		Where("id = ?", 1).Row()
	err = row.Scan(&status)
	return
}

//监听8025端口
func SMTPListen() {
	listen, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 8025, Zone: ""})

	if err != nil {
		config.GetLogger().Warnw("监听端口失败",
			"err: ", err,
		)
		return
	}
	config.GetLogger().Info("正在监听8025端口")
	defer listen.Close()

	config.GetLogger().Info("准备接收来自8025端口的数据")
	smtpReceive(listen)

}

//接收8025端口进来的数据
func smtpReceive(listen *net.TCPListener) {

	for {
		//判断服务器服务启停状态
		stat, _ := GetServerStatus()
		if stat == "F" {
			return
		}
		//接收连接
		conn, err := listen.AcceptTCP()
		defer conn.Close()

		//客户端连接异常
		if err != nil {
			config.GetLogger().Warnw("客户端连接异常",
				"err: ", err.Error(),
			)
			continue
		}
		_, _ = conn.Write([]byte("220 Glad to serve you!" + CRLF))
		config.GetLogger().Info("220 Glad to serve you!")

		config.GetLogger().Info("新客户端", conn.RemoteAddr().String())

		go func() {
			//创建待处理字节切片
			tmpBuffer := make([]byte, 0)
			//创建channel通信管道
			readerChannel := make(chan []byte, 16)
			mail := &Mail{}
			mail.Init()
			//channel信息接收端
			go smtpReader(readerChannel, conn, mail)
			config.GetLogger().Info("正在试图从连接中读取信息并进行响应")

			//创建输出缓冲切片
			data := make([]byte, 1024)

			for {
				//从Socket读取数据
				i, err := conn.Read(data)
				//读取失败发生错误
				if err != nil {
					config.GetLogger().Warnw("读取发生错误",
						"err: ", err.Error(),
					)
					return
				}
				//用自定义协议处理字节

				//fmt.Println(string(data))
				//fmt.Println(string(tmpBuffer))
				tmpBuffer = utils.DepackMail(append(tmpBuffer, data[:i]...), readerChannel)

			}
		}()

	}
}

//针对TCP连接传过来的数据报文进行响应
func smtpReader(readerChannel chan []byte, conn *net.TCPConn, mail *Mail) {

	for {
		select {
		case data := <-readerChannel:
			//fmt.Println(string(data))
			switch strings.ToUpper(string(data[0:4])) {

			case "HELO":
				//这个命令用于说明自己是SMTP客户端身份，参数包含客户端的域名(domain)
				//如果收到回复OK，说明发送者和接收者处于初始状态，所有的状态表和缓存区都被清零

				matchResult, _ := regexp.MatchString(`HELO \S+`, strings.ToUpper(string(data)))
				//参数错误
				if !matchResult {
					_, _ = conn.Write([]byte("501 Parameter Error" + CRLF))
					config.GetLogger().Info("501 Parameter Error")
					break
				}
				domain := strings.Split(string(data), SP)[1]
				mail.domain = domain
				_, _ = conn.Write([]byte("250 Welcome!" + CRLF))
				config.GetLogger().Info("250 Welcome!")
				break
			case "EHLO":
				//EHLO命令同HELO

				matchResult, _ := regexp.MatchString(`EHLO \S+`, strings.ToUpper(string(data)))
				//参数错误
				if !matchResult {
					_, _ = conn.Write([]byte("501 Parameter Error" + CRLF))
					config.GetLogger().Info("501 Parameter Error")
					break
				}
				domain := strings.Split(string(data), SP)[1]
				mail.domain = domain
				_, _ = conn.Write([]byte("250 Welcome!" + CRLF))
				config.GetLogger().Info("250 Welcome!")
				break
			case "MAIL":
				//这个命令的参数是发送者邮箱<reverse-path>，参数中有 FROM 关键字
				//这个命令会清空之前的发送者邮箱、接收者邮箱和邮件数据

				matchResult, _ := regexp.MatchString(`MAIL FROM:\S+`, strings.ToUpper(string(data)))
				//参数错误
				if !matchResult {
					_, _ = conn.Write([]byte("501 Parameter error" + CRLF))
					config.GetLogger().Info("501 Parameter Error")
					break
				}

				//判断邮箱是否有效
				effect, errs := Verify(strings.Split(string(data), ":")[1])
				if errs != nil {
					config.GetLogger().Warnw("验证邮箱发生错误",
						"err: ", errs.Error(),
					)
					_, _ = conn.Write([]byte("554 Operate Failed" + CRLF))
					config.GetLogger().Info("554 Operate Failed")
					break
				}
				if !effect {
					config.GetLogger().Warnw("此邮箱无效: ", strings.Split(string(data), ":")[1])
					_, _ = conn.Write([]byte("553 Mailbox is Invalid" + CRLF))
					config.GetLogger().Info("553 Mailbox is Invalid")
					break
				}

				//添加发件人
				mail.from = strings.Split(string(data), ":")[1]

				_, _ = conn.Write([]byte("250 OK" + CRLF))
				config.GetLogger().Info("250 OK")
				break
			case "RCPT":
				//用于指定一个邮件接收者，参数中有TO 关键字，指定多个接收者通过重复使用这个命令
				matchResult, _ := regexp.MatchString(`RCPT TO:\S+`, strings.ToUpper(string(data)))

				//参数错误
				if !matchResult {
					_, _ = conn.Write([]byte("501 Parameter error" + CRLF))
					config.GetLogger().Info("501 Parameter Error")
					break
				}

				//判断邮箱是否有效
				effect, errs := Verify(strings.Split(string(data), ":")[1])
				if errs != nil {
					config.GetLogger().Warnw("验证邮箱发生错误",
						"err: ", errs.Error(),
					)
					_, _ = conn.Write([]byte("554 Operate Failed" + CRLF))
					config.GetLogger().Info("554 Operate Failed")
					break
				}
				if !effect {
					config.GetLogger().Warnw("此邮箱无效: ", strings.Split(string(data), ":")[1])
					_, _ = conn.Write([]byte("553 Mailbox is Invalid" + CRLF))
					config.GetLogger().Info("553 Mailbox is Invalid")
					break
				}

				//添加一个或多个收信人
				mail.to = append(mail.to, strings.Split(string(data), ":")[1])
				_, _ = conn.Write([]byte("250 OK" + CRLF))
				config.GetLogger().Info("250 OK")
				break
			case "DATA":
				//这个命令没有参数，告诉服务器接着要发送邮件内容。
				//邮件内容包含邮件标题项（message header section）和邮件正文（message body），
				//标题项（Header Fields ）是以项目名（field name）为行的起点，接着是冒号(":")，跟着是内容（field body）以回车换行结束（CRLF）

				matchResult, _ := regexp.MatchString(`DATA`, strings.ToUpper(string(data)))
				//命令错误
				if !matchResult {
					_, _ = conn.Write([]byte("503 Wrong Command Sequence" + CRLF))
					config.GetLogger().Info("503 Wrong Command Sequence")
					break
				}

				//开始邮件输入，以"."结束
				_, _ = conn.Write([]byte("354 OK" + CRLF))
				config.GetLogger().Info("354 OK")
				break
			case "QUIT":
				matchResult, _ := regexp.MatchString(`QUIT`, strings.ToUpper(string(data)))
				//命令错误
				if !matchResult {
					_, _ = conn.Write([]byte("503 Wrong Command Sequence" + CRLF))
					config.GetLogger().Info("503 Wrong Command Sequence")
					break
				}

				//使用事务将邮件保存到数据库
				tx := Db.Begin()
				for i := 0; i < len(mail.to); i++ {
					mails := new(UserMails)
					mails.UserMail = mail.from
					mails.EmailFrom = mail.from
					mails.EmailTo = mail.to[i]
					mails.EmailSubject = mail.subject
					mails.EmailContent = mail.content
					mails.EmailSize = mail.size
					mails.EmailRead = "F"
					mails.EmailFromDel = "F"
					mails.EmailToDel = "F"
					mails.EmailTime = mail.date
					err := tx.Table("emails").Create(&mails).Error
					if err != nil {
						config.GetLogger().Warnw("邮件发送失败",
							"err: ", err,
						)
						tx.Rollback()
						_, _ = conn.Write([]byte("554 Operation failed" + CRLF))
						break
					}
				}
				tx.Commit()
				//fmt.Println(mail)
				//mail.Init()
				_, _ = conn.Write([]byte("221 Nice to meet you!" + CRLF))
				config.GetLogger().Info("221 Nice to meet you!")
				_ = conn.Close()

				break
			default:

				//接收数据报文并获取数据大小和时间
				mail.data = mail.data + string(data)
				mail.size = len(mail.data)
				mail.date = time.Now().Format("2006-01-02-15-04-05")

				//fmt.Println("邮件内容为：" + mail.data)

				//判断报文是否结束，结束条件为回车换行符的前一个字符为"."
				if len(mail.data) < 4 && mail.data[len(mail.data)-3:] != CRLF+"." {
					_, _ = conn.Write([]byte("503 Wrong Command Sequence" + CRLF))
					config.GetLogger().Info("503 Wrong Command Sequence")
				} else if mail.data[len(mail.data)-3:] == CRLF+"." {
					//解析数据报文中的标题
					//fmt.Println(mail.data)
					//matchResult, _ := regexp.MatchString(`\S*\s*subject:\s*\S*:`, mail.data)
					//if matchResult {
					//	mail.subject = strings.Split(strings.Split(mail.data, "subject:")[1],"\n")[0]
					//} else {
					//	mail.subject = "无标题"
					//}

					mail.subject = strings.Split(strings.Split(mail.data, "subject:")[1], "\n")[0]

					//fmt.Println(mail.data)
					//解析数据报文中的正文
					if len(strings.Split(mail.data, "subject:")) > 1 {
						for i := 2; i < len(strings.Split(strings.Split(mail.data, "subject:")[1], CRLF)); i++ {
							mail.content += strings.Split(strings.Split(mail.data, "subject:")[1], CRLF)[i]
						}
					} else {
						mail.content += ""
					}
					_, _ = conn.Write([]byte("250 Finish" + CRLF))
					config.GetLogger().Info("250 Finish")
				}

				break
			}
		}
	}
}
